import { Badge } from '/components/badge'
import { Tabs } from 'nextra/components'
import { Callout } from 'nextra/components'

# Method Configuration

These are the methods used to configure the Umo Editor, including the document save method, file upload method, file deletion method, and the method for handling data returned by the AI Assistant, among others.

## Default Methods

```js
{
  async onSave(content, page, document) {
    throw new Error('Key "onSave": Please set the save method')
  },
  async onFileUpload(file) {
    if (!file) throw new Error('File not found')
    throw new Error('Key "onFileUpload": Please set the upload method')
  },
  onFileDelete(id, url) {
    console.error('The file has been deleted. Please configure the onFileDelete to completely delete the file from the server.')
  },
  async onAssistant(payload, content) {
    throw new Error('Key "onAssistant": Please set the onAssistant method')
  },
  async onCustomImportWordMethod(file) {
    throw new Error('Key "onCustomImportWordMethod": Please set the onAssistant method')
  },
}
```

## Configuration Item Descriptions

### onSave

**Description**: Configure the document save method. This method is called when the user saves the document, saving it to the server.

**Type**: `AsyncFunction`、 `Promise`.

**Parameters**:

- `content`: Document content.
- `page`: Page information.
- `document`: Document information.

**Return Value**: Return true if save is successful, otherwise return false or throw an error.

**Example**:

The following code uses [Axios](https://axios-http.com/) to demonstrate how to configure the onSave method to save the document to the server:

<Tabs items={['Global Configuration', 'SFC Configuration']}>
<Tabs.Tab>
```js
import axios from 'axios'
import { useUmoEditor } from '@umoteam/editor'

const options = {
  async onSave(content, page, document) => {
    const res = await axios.post('/api/save', {
      content,
      page,
      document,
    },{
      headers: {
        'Content-Type': 'application/json',
      }
    });

    // Return true if save is successful, otherwise return false or throw an error
    return true; 
  },
};

app.use(useUmoEditor, options)
```
</Tabs.Tab>
<Tabs.Tab>
```vue
<template>
  <umo-editor 
    @save="onSave" 
  />
</template>
 
<script setup>
import { UmoEditor } from '@umoteam/editor'
import axios from 'axios'

const onSave = async (content, page, document) => {
  const res = await axios.post('/api/save', {
    content,
    page,
    document,
  },{
    headers: {
      'Content-Type': 'application/json',
    }
  });

  // Return true if save is successful, otherwise return false or throw an error
  return true; 
}
</script>
```
</Tabs.Tab>
</Tabs>

### onFileUpload

**Description**: Configure the file upload method. This method is triggered when the user inserts a file, uploading it to the server.

**Type**: `AsyncFunction`、 `Promise`.

**Parameters**:

- `file`: File object.

**Return Value**:

- `id`: The file id returned after a successful upload.
- `url`: The file url returned after a successful upload.

**Example**:

The following code uses [Axios](https://axios-http.com/) to demonstrate how to configure the onFileUpload method to upload files to the server:

<Tabs items={['Global Configuration', 'SFC Configuration']}>
<Tabs.Tab>
```js
import axios from 'axios'
import { useUmoEditor } from '@umoteam/editor'

const options = {
  async onFileUpload(file) => {
    let formData = new FormData();
    // Add the file to the formData object
    formData.append('file', file);
    const res = await axios.post('/api/file-upload', formData,{
      headers: {
        'Content-Type': 'multipart/form-data',
      }
    });

    // Return {id, url} if save is successful, otherwise return false or throw an error
    return {
      id: res.data.id,
      url: res.data.url,
    }; 
  },
};

app.use(useUmoEditor, options)
```
</Tabs.Tab>
<Tabs.Tab>
```vue
<template>
  <umo-editor 
    @file-upload="onFileUpload" 
  />
</template>
 
<script setup>
import { UmoEditor } from '@umoteam/editor'
import axios from 'axios'

const onFileUpload = async (file) => {
  let formData = new FormData();
  // Add the file to the formData object
  formData.append('file', file);
  const res = await axios.post('/api/file-upload', formData,{
    headers: {
      'Content-Type': 'multipart/form-data',
    }
  });

  // Return {id, url} if save is successful, otherwise return false or throw an error
  return {
    id: res.data.id,
    url: res.data.url,
  }; 
}
</script>
```
</Tabs.Tab>
</Tabs>

### onFileDelete

**Description**: Configure the file deletion method. This method is triggered when the user deletes a file, deleting it from the server.

**Type**: `Function` .

**Parameters**:

- `id`: File id.
- `url`: File url.

**Example**:

The following code uses [Axios](https://axios-http.com/) to demonstrate how to configure the onFileDelete method to delete a file from the server:

<Tabs items={['Global Configuration', 'SFC Configuration']}>
<Tabs.Tab>
```js
import axios from 'axios'
import { useUmoEditor } from '@umoteam/editor'

const options = {
  onFileDelete(id, url) => {
    axios.delete(`/api/file/${id}`);
  },
};

app.use(useUmoEditor, options)
```
</Tabs.Tab>
<Tabs.Tab>
```vue
<template>
  <umo-editor 
    @file-delete="onFileDelete" 
  />
</template>
 
<script setup>
import { UmoEditor } from '@umoteam/editor'
import axios from 'axios'

const onFileDelete = (id, url) => {
  axios.delete(`/api/file/${id}`);
}
</script>
```
</Tabs.Tab>
</Tabs>

### onAssistant <Badge theme="success">New in v3.0.0</Badge>

**Description**: Configures the method for handling data returned by the AI Assistant; see more details in [AI Assistant](../assistant).

**Type**: `AsyncFunction`, `Promise`.

**Parameters**:

- `payload`: The request parameters for the AI Assistant, which can be passed to the AI model.
  1. `payload.lang`: `String`, the current interface language.
  2. `payload.input`: `String`, the text content selected by the user.
  3. `payload.command`: `String`, the command entered by the user.
  4. `payload.output`: `String`, the format of the content the AI Assistant expects to receive. Possible values: `rich-text`, `text`.
- `content`: The current document content. You can pass the document content to the AI model, but note that very long documents may cause the AI model to fail to process. If you're using a commercial AI model, an excessively long token input will also lead to significantly increased costs.
  1. `content.text`: `String`, the text content of the current document.
  2. `content.html`: `String`, the HTML content of the current document.
  3. `content.json`: `Object`, the JSON content of the current document.

**Example**:

The following example demonstrates how to configure the `onAssistant` method, passing the document content to the AI model, processing it, and returning the results from the AI model. This example uses calling the OpenAI model as an illustration.

OpenAI's API specification has become a de facto industry standard. In fact, many AI models support being invoked through the [`OpenAI SDK`](https://www.npmjs.com/package/openai).

<Tabs items={['Global Configuration', 'SFC Configuration']}>
<Tabs.Tab>
```js
import { useUmoEditor } from '@umoteam/editor'
import OpenAI from 'openai'

const options = {
  async onAssistant(payload, content) {
    console.log(payload, content)
    const { command, lang, input, output } = payload
    const client = new OpenAI({
      baseURL: '...', // Your OpenAI base URL here
      apiKey: '...', // Your OpenAI API key here
      dangerouslyAllowBrowser: true, // Allow using OpenAI SDK in the browser
    })
    const langs = {
      'en-US': 'English',
      'zh-CN': 'Chinese',
    }
    const options = {
      stream: true,
      model: 'moonshot-v1-8k',
      messages: [
        {
          role: 'system',
          content: `You are a document assistant. Based on the user's input text or HTML content, and the corresponding operation command, generate the required document content. Requirements are as follows: 1. If the command does not require translation, respond in ${langs[lang]}; otherwise, respond in the language requested by the user; 2. Return in ${output === 'rich-text' ? 'rich text (HTML)' : 'plain text (stripping out HTML tags)'} format; 3. If you do not understand the user's command, prepend "[ERROR]: " to the returned content; 4. Do not return any other extraneous content beyond this.`,
        },
        {
          role: 'user',
          content: `Perform the following operation: 【${command}】.\n${input}`,
        },
      ],
    }
    const completion = await client.chat.completions.create(options)
    const stream = new ReadableStream({
      async start(controller) {
        for await (const chunk of completion) {
          controller.enqueue(chunk.choices[0]?.delta?.content || '')
        }
        controller.close()
      },
    })
    return stream
  },
};

app.use(useUmoEditor, options)
```
</Tabs.Tab>
<Tabs.Tab>
```vue
<template>
  <umo-editor 
    @assistant="onAssistant" 
  />
</template>
 
<script setup>
import { UmoEditor } from '@umoteam/editor'
import OpenAI from 'openai'

const onAssistant = (payload, content) => {
  console.log(payload, content)
  const { command, lang, input, output } = payload
  const client = new OpenAI({
    baseURL: '...', // Your OpenAI base URL here
    apiKey: '...', // Your OpenAI API key here
    dangerouslyAllowBrowser: true, // Allow using OpenAI SDK in the browser
  })
  const langs = {
    'en-US': 'English',
    'zh-CN': 'Chinese',
  }
  const options = {
    stream: true,
    model: '...', // Your OpenAI model here
    messages: [
      {
        role: 'system',
        content: `You are a document assistant. Based on the user's input text or HTML content, and the corresponding operation command, generate the required document content. Requirements are as follows: 1. If the command does not require translation, respond in ${langs[lang]}; otherwise, respond in the language requested by the user; 2. Return in ${output === 'rich-text' ? 'rich text (HTML)' : 'plain text (stripping out HTML tags)'} format; 3. If you do not understand the user's command, prepend "[ERROR]: " to the returned content; 4. Do not return any other extraneous content beyond this.`,
      },
      {
        role: 'user',
        content: `Perform the following operation: 【${command}】.\n${input}`,
      },
    ],
  }
  const completion = await client.chat.completions.create(options)
  const stream = new ReadableStream({
    async start(controller) {
      for await (const chunk of completion) {
        controller.enqueue(chunk.choices[0]?.delta?.content || '')
      }
      controller.close()
    },
  })
  return stream
}
</script>
```
</Tabs.Tab>
</Tabs>

<Callout type="error">
**Note:** The above code is provided for demonstration purposes only. In practical applications, sensitive information such as `apiKey` should not be exposed on the client side. AI model calls should be made through backend services, with `apiKey` stored on the server to protect sensitive information. The logic of the above code should also be placed in the backend service as much as possible to reduce request loads.
</Callout>

**Return Value**：`String`、`ReadableStream`。

### onCustomImportWordMethod <Badge theme="success">New in v3.1.0</Badge>

**Description**: A custom method for importing Word documents. If you want to use a custom import method, you need to set the `toolbar.importWord.useCustomMethod` option to `true`. See [Toolbar Configuration](../options/toolbar#importword).

**Type**: `AsyncFunction`, `Promise`.

**Parameter**: `File`

**Return Value**: The same data format as returned by [Mammoth.convertToHtml()](https://www.npmjs.com/package/mammoth#api). Example:

```js
{
  value: '<p>Hello world</p>',
  messages: [
    {
      type: 'success', 
      message: 'Converted 1 paragraph',
    },
  ],
}
```
